#include "stdafx.h"
#include "Copier.h"

static const LONGLONG kMegabyte = 0x100000;


Copier::Copier()
{
	mCopyConverterPtr = nullptr;
	mMainWindow = 0;
}


Copier::~Copier()
{
	Cancel();
}

// ------------------------------------------------

void Copier::SetMainWindow(HWND inWindow)
{
	mMainWindow = inWindow;
}

void Copier::SetRange(LONGLONG inBegin, LONGLONG inEnd)
{
	mRangeBegin = inBegin;
	mRangeEnd = inEnd;
}

bool Copier::PrepareStart(CopyMode inCopyMode)
{
	Cancel();

	switch (inCopyMode)
	{
	case CopyMode::kWysiwyg:
		mCopyConverterPtr = new CopyConverterWysiwyg(mRangeBegin);
		break;
	case CopyMode::kAsHexDigits:
		mCopyConverterPtr = new CopyConverterHexDigits;
		break;
	case CopyMode::kAsCStyleAscii:
		mCopyConverterPtr = new CopyConverterCStyleAscii;
		break;
	case CopyMode::kAsUtf8:
		mCopyConverterPtr = new CopyConverterUtf8;
		break;
	case CopyMode::kAsUtf16LE:
		mCopyConverterPtr = new CopyConverterUtf16LE;
		break;
	case CopyMode::kAsUtf16BE:
		mCopyConverterPtr = new CopyConverterUtf16BE;
		break;
	}
	if (nullptr == mCopyConverterPtr)
	{
		return false;
	}

	LONGLONG minClipboardLength, maxClipboardLength;
	if (!mCopyConverterPtr->InitWithSelectionLength(mRangeEnd - mRangeBegin,
		minClipboardLength, maxClipboardLength))
	{
		delete mCopyConverterPtr;
		mCopyConverterPtr = nullptr;
		MessageBox(mMainWindow, L"Cannot copy", L"Copying to clipboard",
			(MB_OK | MB_ICONSTOP));
		return false;
	}

	if (minClipboardLength >= kMegabyte &&
		!DialogContinueWithBigData(minClipboardLength))
	{
		Cancel();
		return false;
	}

	mCurrentAddress = mRangeBegin;
	return true;
}

void Copier::Continue(FileHandler& inFileHandler)
{
	int step = mCopyConverterPtr->GetEstimatedBytesPerLine();
	ByteVector v(step);
	bool isAtTheEnd = false;
	bool isCancelled = false;
	CopyConverter::ErrorCode error = CopyConverter::kOk;

	while (mCurrentAddress < mRangeEnd)
	{
		int len = step;
		if (mCurrentAddress + step > mRangeEnd)
		{
			len = int(mRangeEnd - mCurrentAddress);
		}

		int len1 = len;
		inFileHandler.CopyBytes(mCurrentAddress, v.data(), len1);
		if (len1 != len)
		{
			::MessageBoxA(mMainWindow,
				"Fail copy to clipboard: a problem with file reading",
				"Copying", MB_OK | MB_ICONSTOP);
			isCancelled = true;
			break;
		}

		error = mCopyConverterPtr->WriteData(v.data(), len1);
		if (CopyConverter::kWriteFail == error)
		{
			::MessageBoxA(mMainWindow, 
				"Fail copy to clipboard: a problem with writing to clipboard",
				"Copying", MB_OK | MB_ICONSTOP);
			isCancelled = true;
			break;
		}
		else if (CopyConverter::kBadCharacter == error)
		{
			char text[100];
			::sprintf_s(text, "Copy operation is cancelled.\nBad character at address: %.4LLx",
				(mCurrentAddress + len1));
			::MessageBoxA(mMainWindow, text, "Copying", MB_OK | MB_ICONSTOP);
			isCancelled = true;
			break;
		}
		else if (CopyConverter::kNullCharacter == error)
		{
			char text[100];
			::sprintf_s(text, "Copy operation is cancelled.\nNULL character at address: %.4LLx",
				(mCurrentAddress + len1));
			::MessageBoxA(mMainWindow, text, "Copying", MB_OK | MB_ICONSTOP);
			isCancelled = true;
			break;
		}
		mCurrentAddress += len1;

		if (!inFileHandler.AreTwoAddressesWithinOneCachedChunk(mCurrentAddress, mRangeEnd))
		{
			inFileHandler.MakeReadInAdvance(mCurrentAddress, true);
			if (inFileHandler.IsReadingAsync())
			{
				break;
			}
		}
	}

	if (isCancelled)
	{
		Cancel();
	}
}

float Copier::GetPercentageDone() const
{
	float ratio = 1.0f;
	if (auto len = mRangeEnd - mRangeBegin)
	{
		ratio = float(mCurrentAddress - mRangeBegin) / float(len);
	}

	return ratio * 100.0f;
}

void Copier::Finish(HWND inOwnerWindow)
{
	HGLOBAL dataHandle = mCopyConverterPtr->GiveData();
	bool isWide = mCopyConverterPtr->IsWideCharacterData();

	bool isDataTransfered = false;
	BOOL isClipboardClosed = false;
	if (0 != dataHandle && 0 != mMainWindow && ::OpenClipboard(inOwnerWindow))
	{
		if (::EmptyClipboard())
		{
			UINT dataFormat = (isWide ? CF_UNICODETEXT : CF_TEXT);
			isDataTransfered = (nullptr != ::SetClipboardData(dataFormat, dataHandle));
		}

		isClipboardClosed = ::CloseClipboard();
	}
	if (!isDataTransfered && 0 != dataHandle)
	{
		::GlobalFree(dataHandle);
	}
	
	if (!isDataTransfered || !isClipboardClosed)
	{
		::MessageBoxA(inOwnerWindow,
			"Fail copy to clipboard: a problem with writing to clipboard",
			"Copying", MB_OK | MB_ICONSTOP);
	}
	Cancel();
}

void Copier::Cancel()
{
	delete mCopyConverterPtr;
	mCopyConverterPtr = nullptr;
}

bool Copier::IsDataReady() const
{
	return (mCurrentAddress == mRangeEnd);
}
bool Copier::IsCopying() const
{
	return (nullptr != mCopyConverterPtr);
}


// ---- private ----

bool Copier::DialogContinueWithBigData(LONGLONG inLength)
{
	UINT x = UINT(inLength / kMegabyte);
	UINT powerOfTen = 1;
	for (UINT y = x; y >= 20; y /= 10)
	{
		powerOfTen *= 10;
	}
	x = (x / powerOfTen) * powerOfTen;

	// alert that the clipboard data would be too big
	char buf[100];
	sprintf_s(buf, "The data being copied to the clipboard is quite big:\n"
		"  more than %u MB", x);
	int answer = ::MessageBoxA(mMainWindow, buf,
		"Copying to clipboard", MB_OKCANCEL | MB_ICONINFORMATION);

	return (answer == IDOK);
}
